package util;

import earlyDetection.EarlyDetector;
import eval.EventEmiter;
import jdd.bdd.BDD;
import jdd.bdd.BDDIO;
import jdd.util.Array;
import network.Device;
import network.Network;
import network.Port;
import verifier.Ports.Ports;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.*;

public class Util {
    public static int counter = 0;

    public static int crossBDDLoad(BDD originBDD, int originValue, BDD targetBDD) {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            BDDIO.save(originBDD, originValue, bos);
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            return targetBDD.ref(BDDIO.load(targetBDD, bis));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return 0;
    }

    public static int crossBDDLoad(byte[] bytes, BDD targetBDD) {
        try {
            ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
            return targetBDD.ref(BDDIO.load(targetBDD, bis));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return 0;
    }

    public static HashSet<Integer> crossBDDLoadSet(HashSet<byte[]> bytes, BDD targetBDD) {
        HashSet<Integer> out = new HashSet<>();
        for (byte[] b : bytes) {
            out.add(crossBDDLoad(b, targetBDD));
        }
        return out;
    }

    static boolean hasLoop = false;
    public static void checkLoop(HashSet<Device> devices, HashMap<Port, HashSet<Integer>> networkModel) {
        for (Device device : devices) {
            if (Util.hasLoop) return;
            traverse(device, null, new HashSet<>(), networkModel, devices); // null represents the universal set
        }
    }


    private static void traverse(Device current, HashSet<Integer> predicates, HashSet<Device> history, HashMap<Port, HashSet<Integer>> networkModel, Set<Device> closed) {
        if (Util.hasLoop) return;
        if (predicates != null && predicates.isEmpty()) return;
        if (history.contains(current)) {
            System.out.println("found global loop at: " + (System.nanoTime() - EventEmiter.startAt));
            Util.hasLoop = true;
            return;
        }

        history.add(current);
        for (Port egress : current.getPorts()) {
            // if egress is default, alter blackhole
            Device t = egress.getPeerDevice();
            if (egress.getName().equals("default") || t == null || !closed.contains(t)) {
                continue;
            }
            HashSet<Integer> labels = networkModel.get(egress), intersection;
            if (labels != null) {
                if (predicates != null) {
                    intersection = new HashSet<>(predicates);
                    intersection.retainAll(labels);
                } else {
                    intersection = new HashSet<>(labels);
                }

                traverse(t, intersection, history, networkModel, closed);
            }
        }
        history.remove(current);
    }

    public static HashSet<Integer> loopPset = new HashSet<>();
    public static void allPair(Network network, HashMap<Port, HashSet<Integer>> model) {
        loopPset.clear();
        for (Device device : network.getAllDevices()) {
            traverse(model, device, null, new ArrayList<>()); // null represents the universal set
        }
    }

    public static void traverse(HashMap<Port, HashSet<Integer>> model, Device current, HashSet<Integer> pset, ArrayList<Device> history) {
        if (pset != null && pset.size() == 0) return;
        if (history.contains(current)) {
            loopPset.addAll(pset);
            return;
        }
        history.add(current);
        for (Port egress : current.getPorts()) {
            // if egress is default, alter blackhole
            Device t = egress.getPeerDevice();
            if (t == null) { // send to black hole (default) or an external port
                continue;
            }
            HashSet<Integer> labels = model.get(egress), intersection;
            if (labels != null) {
                if (pset != null) {
                    intersection = new HashSet<>(pset);
                    intersection.retainAll(labels);
                } else {
                    intersection = new HashSet<>(labels);
                }

                traverse(model, t, intersection, history);
            }
        }
        history.remove(current);
    }

}
